﻿#include "pch.h"
#if _QG_USE_GL
#include "qgdrv_gl_rdh.h"
#include "qgdrv_gl_cnv.h"

//
void GLRdh::CheckBindBuffer(GLenum type, GLuint id) throw()
{
	switch (type)
	{
		case GL_ARRAY_BUFFER:
			if (_sion.buf_array != id)
			{
				glBindBuffer(GL_ARRAY_BUFFER, id);

				_sion.buf_array = id;
			}
			break;

		case GL_ELEMENT_ARRAY_BUFFER:
			if (_sion.buf_element_array != id)
			{
				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id);

				_sion.buf_element_array = id;
			}
			break;

		case GL_PIXEL_UNPACK_BUFFER:
			if (_sion.buf_pixel_unpack != id)
			{
				glBindBuffer(GL_PIXEL_UNPACK_BUFFER, id);

				_sion.buf_pixel_unpack = id;
			}
			break;
	}
}

//
void GLRdh::_CommitDrawLayout() throw()
{
	// GL은 BindBuffer가 시도 때도 없이 쓰여서 무조건 돌려야함
	GLShader* shd = _pend.shd_ptr;
	GLLayout* lo = _pend.los_ptr;
	kint s, i;
	bool is[QGGL_MAX_DRAWLAYOUT_ATTRIB] = {false, };

	for (s = 0; s < QGSGVERT_MAX_VALUE; s++)
	{
		GLBuffer* buf = _pend.vb_ptrs[s];
		GLLayout::Element* stelm = lo->_sg_elm[s];
		kint stcnt = lo->_sg_cnt[s];

		if (buf != NULL)
		{
			// 버퍼 설정
			kint stride = lo->_stage[s];
			GLuint buffer = (GLuint)buf->GetDescription();

			CheckBindBuffer(GL_ARRAY_BUFFER, buffer);

			// 레이아웃
			for (i = 0; i < stcnt; i++)
			{
				const GLLayout::Element& le = stelm[i];
				GLShader::AttribInfo* pav = shd->FindAttribInfo(le.usage, le.index);

				if (pav != NULL && pav->attrib < QGGL_MAX_DRAWLAYOUT_ATTRIB)
				{
					GLuint attrib = pav->attrib;
					GLLayoutProperty& lp = _sion.los_props[attrib];
					kpointer pointer = ((kbyte*)NULL + le.offset);

					is[attrib] = true;

					if (!lp.enable)
					{
						glEnableVertexAttribArray(attrib);

						lp.enable = true;
					}

					if (lp.pointer != pointer ||
						lp.buffer != buffer ||
						lp.stride != stride ||
						lp.size != le.size ||
						lp.format != le.format ||
						lp.normalized != le.normalized)
					{
						glVertexAttribPointer(attrib, le.size, le.format, le.normalized, stride, pointer);

						lp.pointer = pointer;
						lp.buffer = buffer;
						lp.stride = stride;
						lp.size = le.size;
						lp.format = le.format;
						lp.normalized = le.normalized;
					}
				}
			}
		}
		else
		{
			// 버퍼없는 속성에 대한 처리
			for (i = 0; i < stcnt; i++)
			{
				const GLLayout::Element& le = stelm[i];
				GLShader::AttribInfo* pav = shd->FindAttribInfo(le.usage, le.index);

				if (pav != NULL && pav->attrib < QGGL_MAX_DRAWLAYOUT_ATTRIB)
				{
					GLuint attrib = pav->attrib;
					GLLayoutProperty& lp = _sion.los_props[attrib];

					if (lp.enable)
					{
						glDisableVertexAttribArray(attrib);

						lp.enable = false;
					}

					GLfloat tmp[4] = {0.0f, };

					glVertexAttrib4fv(attrib, tmp);
				}
			}
		}
	}

	// 안쓰는 거 거시기
	for (s = 0; s < QGGL_MAX_DRAWLAYOUT_ATTRIB; s++)
	{
		if (!is[s] && _sion.los_props[s].enable)
		{
			glDisableVertexAttribArray(s);

			_sion.los_props[s].enable = false;
		}
	}
}

//
void GLRdh::_CommitDrawLayoutUp(kpointer buffer, kuint stride) throw()
{
	GLShader* shd = _pend.shd_ptr;
	GLLayout* lo = _pend.los_ptr;
	kint i;
	bool is[QGGL_MAX_DRAWLAYOUT_ATTRIB] = {false, };

	// 스테이지 0밖에 안씀 -> 버퍼가 하나니까
	GLLayout::Element* stelm = lo->_sg_elm[0];
	kint stcnt = lo->_sg_cnt[0];

	// 음.. 검사해보까 마까
	k_assert_0(stride == lo->_stage[0]);

	// 버퍼 초기화를 안하면 에러난다.
	CheckBindBuffer(GL_ARRAY_BUFFER, 0);

	//
	for (i = 0; i < stcnt; i++)
	{
		const GLLayout::Element& le = stelm[i];
		GLShader::AttribInfo* pav = shd->FindAttribInfo(le.usage, le.index);

		if (pav != NULL && pav->attrib < QGGL_MAX_DRAWLAYOUT_ATTRIB)
		{
			GLuint attrib = pav->attrib;
			GLLayoutProperty& lp = _sion.los_props[attrib];
			kpointer pointer = ((kbyte*)buffer + le.offset);

			is[attrib] = true;

			if (!lp.enable)
			{
				glEnableVertexAttribArray(attrib);

				lp.enable = true;
			}

			if (lp.pointer != pointer ||
				lp.buffer != 0 ||
				lp.stride != (GLsizei)stride ||
				lp.size != le.size ||
				lp.format != le.format ||
				lp.normalized != le.normalized)
			{
				glVertexAttribPointer(attrib, le.size, le.format, le.normalized, stride, pointer);

				lp.pointer = pointer;
				lp.buffer = 0;
				lp.stride = stride;
				lp.size = le.size;
				lp.format = le.format;
				lp.normalized = le.normalized;
			}
		}
	}

	// 안쓰는 거 거시기
	for (i = 0; i < QGGL_MAX_DRAWLAYOUT_ATTRIB; i++)
	{
		if (!is[i] && _sion.los_props[i].enable)
		{
			glDisableVertexAttribArray(i);

			_sion.los_props[i].enable = false;
		}
	}
}

//
qgResObj* GLRdh::_RoamInitVertexLayout(const qbLayoutArray& inz)
{
	static GLenum sFormat[QGDLOT_MAX_VALUE] =
	{
		GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_FLOAT,
		GL_HALF_FLOAT, GL_HALF_FLOAT,
		GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE,
		GL_SHORT, GL_SHORT, GL_SHORT,
		GL_UNSIGNED_BYTE
	};
	static kint sSize[QGDLOT_MAX_VALUE] =
	{
		1, 2, 3, 4,
		2, 4,
		2, 4, 4,
		2, 4, 2,
		4
	};
	static GLboolean sNormalized[QGDLOT_MAX_VALUE] =
	{
		GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE,
		GL_TRUE, GL_TRUE,
		GL_FALSE, GL_FALSE, GL_TRUE,
		GL_FALSE, GL_FALSE, GL_TRUE,
		GL_TRUE
	};
	static GLboolean sConvertFloat[QGDLOT_MAX_VALUE] =
	{
		GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE,
		GL_TRUE, GL_TRUE,
		GL_FALSE, GL_FALSE, GL_TRUE,
		GL_FALSE, GL_FALSE, GL_TRUE,
		GL_TRUE
	};

	//
	GLLayout* base = kobj_create(GLLayout);

	ksize_t i, cnt = inz.elms.size();
	kushort offset[QGSGVERT_MAX_VALUE] = {0, }, count[4] = {0, };
	auto& elas = base->_elas.elms;

	elas.resize(cnt);

	// 복사와 개수 파악
	for (i = 0; i < cnt; i++)
	{
		const qbLayoutElement& e = inz.elms[i];

		count[e.slot]++;

		elas[i] = e;
	}

	// 스테이지 따로 보관
	GLLayout::Element* elms[4] = {NULL, };

	for (i = 0; i < QGSGVERT_MAX_VALUE; i++)
	{
		if (count[i] != 0)
		{
			base->_sg_cnt[i] = count[i];
			base->_sg_elm[i] = k_new_zero(count[i], GLLayout::Element);

			elms[i] = base->_sg_elm[i];
		}
	}

	// 실제 데이터로 만들기
	for (i = 0; i < cnt; i++)
	{
		const qbLayoutElement& e = inz.elms[i];
		GLLayout::Element* u = elms[e.slot]++;
		kushort size = (kushort)qgResSupp::LayoutTypeToSize(e.type);

		u->usage = e.usage;
		u->index = e.index;
		u->format = sFormat[e.type];
		u->attrib = i;
		u->offset = offset[e.slot];
		u->size = sSize[e.type];
		u->normalized = sNormalized[e.type];
		u->convfloat = sConvertFloat[e.type];

		offset[e.slot] += size;
	}

	// 값 
	for (base->_stride = i = 0; i < QGSGVERT_MAX_VALUE; i++)
	{
		base->_stride += offset[i];
		base->_stage[i] = offset[i];
	}

	return base;
}

//
qgResObj* GLRdh::_RoamInitBuffer(kint type, kint stride, kint size, kint flags, kconstpointer data)
{
	// 타입 먼저 검사하자
	GLenum gltype;

	if (type == QGBUFFER_VERTEX)
		gltype = GL_ARRAY_BUFFER;
	else if (type == QGBUFFER_INDEX)
		gltype = GL_ELEMENT_ARRAY_BUFFER;
	else
	{
		// 아앆! 머임
		return NULL;
	}

	// 버퍼 만들거
	GLuint id;

	glGenBuffers(1, &id);

	if (id == 0)
		return NULL;

	// 바인드
	CheckBindBuffer(gltype, id);

	// 유형별 처리 및 버퍼 데이터 만들기
	// : STATIC		-> 한번 설정, 여러번 사용
	// : DYNAMIC	-> 여러번 설정, 여러번 사용
	// : STREAM		-> 한번 설정, 조금만 사용
	// : DRAW		-> 사용자가 설정, 그리기 위해 사용
	// : READ		-> GL이 읽음, 사용자 질의하기 위해 사용
	// : WRITE		-> GL이 읽음, 그리기 위해 사용
	GLenum glusage;
	const GLvoid* ptr;

	if (data != NULL)
	{
		// 데이터 초기화가 들어감, 한번만 쓰기
		glusage = GL_STATIC_DRAW;
		ptr = data;
	}
	else if (K_OFLAG(flags, QGRESF_DYNAMIC))
	{
		glusage = GL_DYNAMIC_DRAW;
		ptr = NULL;
	}
	else
	{
		glusage = GL_STATIC_DRAW;	// 으음... 이렇게 해도 될까나
		ptr = NULL;
	}

	// 데타 쓰거나 말거나, 하튼 초기화
	glBufferData(gltype, (GLsizeiptr)size, ptr, glusage);

	// 준비는 끝났다
	GLBuffer* base = kobj_create(GLBuffer);

	base->_type = (qgBufferType)type;
	base->_flags = flags;
	base->_stride = stride;
	base->_size = size;

	base->_gl_type = gltype;
	base->_gl_usage = glusage;
	base->_immutable = ptr != NULL;

	base->_node_desc = (kuintptr)id;

	return base;
}


//////////////////////////////////////////////////////////////////////////
// 레이아웃
KIMPL_SUBCLASS(GLLayout, "GLLayout");

//
GLLayout::GLLayout() throw()
{
}

//
GLLayout::~GLLayout() throw()
{
	for (kint i = 0; i < 4; i++)
	{
		if (_sg_elm[i])
			k_delete(_sg_elm[i]);
	}
}


//////////////////////////////////////////////////////////////////////////
// 버퍼
KIMPL_SUBCLASS(GLBuffer, "GLBuffer");

//
GLBuffer::GLBuffer() throw()
{
}

//
GLBuffer::~GLBuffer() throw()
{
	if (_node_desc)
	{
		if (_lock != NULL)
			GLBuffer::Unmap();

		if (_slockptr != NULL)
			k_delete(_slockptr);

		GLuint id = (GLuint)_node_desc;
		glDeleteBuffers(1, &id);
	}
}

//
kpointer GLBuffer::Map(kint flags)
{
	// 잠겨 있으면 실패
	if (_lock != NULL)
	{
		kobj_tracep(KLV_ERROR, KERR_MAPPING);
		return NULL;
	}

	// 초기 데이터가 있으면 잠글 수 없음
	if (_immutable)
	{
		kobj_tracep(KLV_ERROR, KERR_MAPPING);
		return NULL;
	}

	//
	GLRdh* rdh = (GLRdh*)qgRdh::Instance;

	// 맵퍼 지원하는거에 따라 다름
	if (qbOpenGL::QglSupportMapBuffer())
	{
		// 바인드 
		rdh->CheckBindBuffer(_gl_type, (GLuint)_node_desc);

		if (!K_OFLAG(flags, QGENTF_READ))
		{
			// 읽기가 없다 -> 쓰기 모드
			// 기존 버퍼를 제거해버림 (DISCARD)
			glBufferData(_gl_type, _size, NULL, _gl_usage);
		}

		//
		GLenum access;

		if (!K_OFLAG(flags, QGENTF_READ))
			access = GL_WRITE_ONLY;
		else if (!K_OFLAG(flags, QGENTF_WRITE))
			access = GL_READ_ONLY;
		else
			access = GL_READ_WRITE;

		_lock = qbOpenGL::QglMapBuffer(_gl_type, access);

		if (_lock == NULL)
		{
			// 헐... 뭐징
			kobj_tracep(KLV_ERROR, KERR_MAPPING);
		}
	}
	else
	{
		if (!_slockptr)
			_slockptr = k_new(_size, kbyte);

		_lock = _slockptr;
	}

	return _lock;
}

//
bool GLBuffer::Unmap()
{
	if (_lock == NULL)
	{
		// 잠겨있지 않음
		kobj_tracep(KLV_ERROR, KERR_MAPPING);
		return false;
	}

	GLRdh* rdh = (GLRdh*)qgRdh::Instance;

	// 바인드
	rdh->CheckBindBuffer(_gl_type, (GLuint)_node_desc);

	if (qbOpenGL::QglSupportMapBuffer())
	{
		// 맵 버퍼 지원
		if (!qbOpenGL::QglUnmapBuffer(_gl_type))
			kobj_tracep(KLV_ERROR, KERR_MAPPING);
	}
	else
	{
		// 맵 버퍼 안됨
		glBufferData(_gl_type, _size, _slockptr, _gl_usage);
		//glBufferSubData(_gl_type, 0, _size, _slockptr);
	}

	//
	_lock = NULL;

	return true;
}

#endif	// _QG_USE_GL

